/*
 * Copyright (c) 2023 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "se_napi_oma_channel.h"

#include <memory>
#include <string>
#include "se_napi_oma_common.h"

namespace OHOS {
namespace SE {
namespace OMAPI {

thread_local napi_ref NapiSEOmaChannel::constRef_ = nullptr;

napi_value JSOmaChannelClassConstructor(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function JSOmaChannelClassConstructor");
    napi_value thisVar = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    NapiSEOmaChannel* omaJSClass = new NapiSEOmaChannel();

    napi_status status = napi_wrap(
        env,
        thisVar,
        omaJSClass,
        [](napi_env env, void *data, void *hint) {
            NapiSEOmaChannel *service = (NapiSEOmaChannel *)data;
            if (service) {
                delete service;
                service = nullptr;
            }
        },
        nullptr,
        nullptr);
    if (status != napi_ok) {
        if (omaJSClass != nullptr) {
            delete omaJSClass;
            omaJSClass = nullptr;
        }
        ErrorLog("napi_wrap failed, object is null. status = %{public}d", status);
        return NapiSEOmaCommon::CreateUndefined(env);
    }
    return thisVar;
}

void NapiSEOmaChannel::DefineSEOmaChannelJsClass(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("getSession", NapiSEOmaChannel::GetSession),
        DECLARE_NAPI_FUNCTION("close", NapiSEOmaChannel::Close),
        DECLARE_NAPI_FUNCTION("isBasicChannel", NapiSEOmaChannel::IsBasicChannel),
        DECLARE_NAPI_FUNCTION("isClosed", NapiSEOmaChannel::IsClosed),
        DECLARE_NAPI_FUNCTION("getSelectResponse", NapiSEOmaChannel::GetSelectResponse),
        DECLARE_NAPI_FUNCTION("transmit", NapiSEOmaChannel::Transmit),
    };
    // define JS class SEOmaChannel, JS_Constructor is the callback function
    napi_value constructor = nullptr;
    napi_define_class(env, "SEOmaChannel", NAPI_AUTO_LENGTH, JSOmaChannelClassConstructor,
        nullptr, sizeof(desc)/sizeof(desc[0]), desc, &constructor);
    napi_create_reference(env, constructor, 1, &constRef_);
}

napi_value NapiSEOmaChannel::GetSession(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaChannel::GetSession:");
    napi_value jsSessionObj = nullptr;
    napi_value thisVar = nullptr;
    NapiSEOmaChannel *napiChannel = nullptr;
    NAPI_CALL(env, napi_get_cb_info(env, info, 0, nullptr, &thisVar, nullptr));
    napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&napiChannel));
    NAPI_ASSERT(env, status == napi_ok, "failed to get napiChannel");
    if (napiChannel == nullptr || napiChannel->channel_ == nullptr || napiChannel->session_ == nullptr) {
        ErrorLog("NapiSEOmaChannel::GetSession napiChannel is nullptr!");
        return NapiSEOmaCommon::CreateUndefined(env);
    }

    // return jsSessionObj
    napi_value constructor = nullptr;
    napi_get_reference_value(env, NapiSEOmaSession::consRef_, &constructor);
    napi_new_instance(env, constructor, 0, nullptr, &jsSessionObj);
    status = napi_wrap(
        env, jsSessionObj, napiChannel->session_,
        [](napi_env env, void *data, void *hint) {
            ErrorLog("NapiSEOmaChannel::GetSession delete JsSession!");
            if (data) {
                NapiSEOmaSession* service = (NapiSEOmaSession*)data;
                if (service) {
                    delete service;
                    service = nullptr;
                }
            }
        },
        nullptr, nullptr);
    if (status != napi_ok) {
        if (napiChannel->session_ != nullptr) {
            delete napiChannel->session_;
            napiChannel->session_ = nullptr;
        }
        ErrorLog("napi_wrap failed, object is null. status = %{public}d", status);
        return NapiSEOmaCommon::CreateUndefined(env);
    }
    DebugLog("Channel::GetSession success");
    return jsSessionObj;
}

napi_value NapiSEOmaChannel::Close(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaChannel::Close:");
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_0;
    napi_value argv[] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NapiSEOmaCommon::CheckArgCountAndThrow(env, argc, ARGV_NUM_0);

    NapiSEOmaChannel *napiChannel = nullptr;
    napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&napiChannel));
    NAPI_ASSERT(env, status == napi_ok, "failed to get napiChannel");
    if (napiChannel == nullptr || napiChannel->channel_ == nullptr) {
        ErrorLog("Close napiChannel is nullptr!");
        return NapiSEOmaCommon::CreateUndefined(env);
    }

    // close channel by call native method, throw exception when failed
    napiChannel->channel_->Close();
    DebugLog("Channel::Close success");
    return NapiSEOmaCommon::CreateUndefined(env);
}

napi_value NapiSEOmaChannel::IsBasicChannel(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaChannel::IsBasicChannel ");
    napi_value result = nullptr;
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_0;
    napi_value argv[] = {nullptr};
    bool isBasicChannel = false;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NapiSEOmaCommon::CheckArgCountAndThrow(env, argc, ARGV_NUM_0);

    NapiSEOmaChannel *napiChannel = nullptr;
    napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&napiChannel));
    NAPI_ASSERT(env, status == napi_ok, "failed to get napiChannel");
    if (napiChannel == nullptr || napiChannel->channel_ == nullptr) {
        ErrorLog("IsBasicChannel napiChannel is nullptr!");
        return NapiSEOmaCommon::CreateUndefined(env);
    }

    // get is basic channel by calling native method, throw exception when faild
    napiChannel->channel_->IsBasicChannel(isBasicChannel);
    napi_get_boolean(env, isBasicChannel, &result);
    DebugLog("Channel::IsBasicChannel:%{public}d", isBasicChannel);
    return result;
}

napi_value NapiSEOmaChannel::IsClosed(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaChannel::isClosed ");
    napi_value result = nullptr;
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_0;
    napi_value argv[] = {nullptr};
    bool isClosed = true;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NapiSEOmaCommon::CheckArgCountAndThrow(env, argc, ARGV_NUM_0);
    // get is closed by calling native method, throw exception when faild

    NapiSEOmaChannel *napiChannel = nullptr;
    napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&napiChannel));
    NAPI_ASSERT(env, status == napi_ok, "failed to get napiChannel");
    if (napiChannel == nullptr || napiChannel->channel_ == nullptr) {
        ErrorLog("IsClosed napiChannel is nullptr!");
        napi_get_boolean(env, isClosed, &result);
        return result;
    }
    napiChannel->channel_->IsClosed(isClosed);
    napi_get_boolean(env, isClosed, &result);
    DebugLog("Channel::IsClosed:%{public}d", isClosed);
    return result;
}

napi_value NapiSEOmaChannel::GetSelectResponse(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaChannel::GetSelectResponse:");
    napi_value result = nullptr;
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_0;
    napi_value argv[] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NapiSEOmaCommon::CheckArgCountAndThrow(env, argc, ARGV_NUM_0);

    // get select response by call native method, throw expection when failed

    std::vector<int32_t> vec = {1};

    NapiSEOmaChannel *napiChannel = nullptr;
    napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&napiChannel));
    NAPI_ASSERT(env, status == napi_ok, "failed to get napiChannel");
    if (napiChannel == nullptr || napiChannel->channel_ == nullptr) {
        ErrorLog("GetSelectResponse napiChannel is nullptr!");
        return NapiSEOmaCommon::CreateUndefined(env);
    }

    // new instance of number array
    std::string selectRes;
    int statusCode = napiChannel->channel_->GetSelectResponse(selectRes);
    NapiSEOmaCommon::CheckSEStatusCodeAndThrow(env, statusCode, "GetSelectResponse");
    NapiSEOmaCommon::ConvertStringToNumberArray(env, result, selectRes);
    DebugLog("NapiSEOmaChannel::GetSelectResponse: %{public}s", selectRes.c_str());
    return result;
}

napi_value NapiSEOmaChannel::Transmit(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaChannel::Transmit:");
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_2;
    napi_value argv[ARGV_NUM_2] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    if (argc <= ARGV_NUM_0 || argc > ARGV_NUM_2) {
        ErrorLog("parameter number error");
        NapiSEOmaCommon::ThrowParameterError(env);
    }
    std::unique_ptr<ChannelContext> asyncContext = std::make_unique<ChannelContext>(env);
    if (asyncContext == nullptr) {
        ErrorLog("asyncContext is nullptr");
        return NapiSEOmaCommon::CreateUndefined(env);
    }
    if (!NapiSEOmaCommon::ParseBytesVector(env, asyncContext->command, argv[ARGV_INDEX_0])) {
        ErrorLog("parse int vector failed");
        NapiSEOmaCommon::ThrowParameterError(env);
    }

    if (argc == ARGV_NUM_2) {
        // callback function
        if (NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_1], napi_function)) {
            DebugLog("transmit callback is not null");
            napi_create_reference(env, argv[ARGV_INDEX_1], 1, &(asyncContext->callbackRef));
        }
    }
    NapiSEOmaChannel *napiChannel = nullptr;
    napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&napiChannel));
    NAPI_ASSERT(env, status == napi_ok, "failed to get napiChannel");
    if (napiChannel == nullptr) {
        ErrorLog("Transmit napiChannel is nullptr!");
        NapiSEOmaCommon::CheckSEStatusCodeAndThrow(env, SE_ILLEGAL_STATE_ERROR, "Transmit");
        return NapiSEOmaCommon::CreateUndefined(env);
    }
    asyncContext->napiChannel = napiChannel;
    DebugLog("Channel::Transmit success");
    return NapiSEOmaCommon::HandleAsyncWork(env, asyncContext.release(),
        "transmit", NativeTransit, NativeTransitCallBack);
}

void NapiSEOmaChannel::NativeTransit(napi_env env, void *data)
{
    DebugLog("Enter function NapiSEOmaChannel::NativeTransit:");
    if (data == nullptr) {
        ErrorLog("NapiSEOmaChannel::NativeTransit data is nullptr");
        return NapiSEOmaCommon::ThrowParameterError(env);
    }
    // call native method
    auto asyncContext = static_cast<ChannelContext *>(data);
    std::string cmd = CommonUtils::BytesArrayToHexString(&asyncContext->command[0], asyncContext->command.size());
    int statusCode = asyncContext->napiChannel->channel_->Transmit(cmd, asyncContext->response);
    asyncContext->errorCode = statusCode;
    if (statusCode == ErrorCode::SE_SUCCESS) {
        asyncContext->resolved = true;
    } else {
        asyncContext->resolved = false;
    }
    DebugLog("NapiSEOmaChannel::NativeTransit send : %{public}s", cmd.c_str());
    DebugLog("NapiSEOmaChannel::NativeTransit recv : %{public}s", asyncContext->response.c_str());
}

void NapiSEOmaChannel::NativeTransitCallBack(napi_env env, napi_status status, void *data)
{
    DebugLog("Enter function NapiSEOmaChannel::NativeTransitCallBack:");
    if (data == nullptr) {
        ErrorLog("NapiSEOmaChannel::NativeTransitCallBack data is nullptr");
        return NapiSEOmaCommon::ThrowParameterError(env);
    }
    ChannelContext *asyncContext = static_cast<ChannelContext *>(data);
    napi_value response = nullptr;
    NapiSEOmaCommon::ConvertStringToNumberArray(env, response, asyncContext->response);
    DoAsyncCallbackOrPromise(env, asyncContext, response);
    DebugLog("Channel::NativeTransitCallBack success");
}
} // namespace OMAPI
}  // namespace SE
}  // namespace OHOS
